﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using MachineSimulator.Interfaces;
using System.Threading;
using OpenNETCF.IoC;
using System.Diagnostics;

namespace SimulatorAdapter
{
    class SimulationService : ISimulationService
    {
        private bool m_enabled;

        public int SimulationTickInterval { get; set; }
        public bool SimulateX { get; set; }
        public bool SimulateY { get; set; }
        public bool SimulatePower { get; set; }
        public bool SimulateDoor { get; set; }
        public bool SimulateEStop { get; set; }
        private int CurrentTick { get; set; }

        private IMachineInterface MachineInterface { get; set; }

        [InjectionConstructor]
        public SimulationService([ServiceDependency]IMachineInterface machineInterface)
        {
            SimulationTickInterval = 500;

            new Thread(SimulationProc)
            {
                IsBackground = true,
                Name = "Simulation Proc"
            }
            .Start();

            MachineInterface = machineInterface;
        }

        ~SimulationService()
        {
            Enabled = false;
        }

        public bool Enabled
        {
            get { return m_enabled; }
            set
            {
                // only set if it's changed
                if (value == Enabled) return;

                if (value)
                {
                    // start simulation
                    MachineInterface.ControllerMode = "AUTOMATIC";
                    MachineInterface.ControllerExecution = "ACTIVE";
                }
                else
                {
                    // stop simulation
                    MachineInterface.ControllerMode = "MANUAL";
                    MachineInterface.ControllerExecution = "STOPPED";
                }

                m_enabled = value;
            }
        }

        private void SimulationProc()
        {
            int start;
            
            Thread.Sleep(2000);

            MachineInterface.ControllerProgram = "SimPart";

            while (true)
            {
                MachineInterface.ControllerMode = "AUTOMATIC";

                start = Environment.TickCount;

                // Do Simulations
                if (CurrentTick < m_ticksperPart)
                {
                    DoCircle();
                }
                else if (CurrentTick < (m_ticksperPart + m_parkTicks))
                {
                    DoPark();
                }
                else if (CurrentTick < (m_ticksperPart + m_parkTicks + m_handlingTicks))
                {
                    DoHandling();
                }
                else if (CurrentTick < (m_ticksperPart + 2 * m_parkTicks + m_handlingTicks))
                {
                    DoUnpark();
                }
                else
                {
                    CurrentTick = -1;
                }

                SetControllerBlock();

                var elapsed = Environment.TickCount - start;
                if ((elapsed >= 0) && (elapsed < SimulationTickInterval))
                {
                    Thread.Sleep(SimulationTickInterval - elapsed);
                }
                CurrentTick++;
            }
        }

        private double m_amplitude = 100;
        private double m_period = 0.2;
        private double m_phaseX = 0;
        private double m_phaseY = 1.57; // pi/2
        private int m_ticksperPart = 31; // pi/period
        private int m_parkTicks = 5;
        private int m_handlingTicks = 20;

        private int m_averageCuttingPower = 70;  // 70 amps
        private int m_movingPower = 12;
        private int m_handlingPower = 8;
        private int m_cuttingPowerFluctuation = 5;  // +/- 5 amps

        private Random m_random = new Random(Environment.TickCount);

        private double x;

        private void DoCircle()
        {
            MachineInterface.ControllerExecution = "ACTIVE";

            MachineInterface.DoorOpen = false;

            // update position
            // amplitude * sin(period * x + phase) + offset
            x = m_amplitude * Math.Sin(m_period * (CurrentTick % m_ticksperPart) + m_phaseX);
            //            Debug.WriteLine(x);
            MachineInterface.X = (int)x;
            var y = m_amplitude * Math.Sin(m_period * (CurrentTick % m_ticksperPart) + m_phaseY);
            MachineInterface.Y = (int)y;

            // update power
            if (m_random.Next(20) == 1) // 5%
            {
                // randomly have high power (> 80 == warning, > 90 == fault)
                if (m_random.Next(3) == 1)
                {
                    MachineInterface.Power = 95;
                }
                else
                {
                    MachineInterface.Power = 81;
                }
            }
            else
            {
                var powerDeviation = m_random.Next(-1 * m_cuttingPowerFluctuation, m_cuttingPowerFluctuation);
                MachineInterface.Power = m_averageCuttingPower + powerDeviation;
            }
        }


        private void DoPark()
        {
            MachineInterface.ControllerExecution = "ACTIVE";
            MachineInterface.Power = m_movingPower;
            var step = 450 / m_parkTicks;
            x -= step;
            MachineInterface.X = (int)x;
        }

        private void DoUnpark()
        {
            MachineInterface.ControllerExecution = "ACTIVE";
            MachineInterface.Power = m_movingPower;
            var step = 450 / m_parkTicks;
            x += step;
            MachineInterface.X = (int)x;
        }

        private void DoHandling()
        {
            MachineInterface.AcknowlegeFaults();
            MachineInterface.ControllerExecution = "READY";
            MachineInterface.Power = m_handlingPower;
            MachineInterface.DoorOpen = true;
        }

        private void SetControllerBlock()
        {
            var index = (CurrentTick / 5) % (M_CODES.Length - 1);
            MachineInterface.ControllerBlock = M_CODES[index];
        }

        private string[] M_CODES = new string[]
        {
            "N035G1X-3.176F.2",
            "N040G0X36.032Z6.254+1",
            "N045Z4.754",
            "N050G1X-3.176",
            "N055G0X36.482Z4.754+1",
            "N060Z3.254",
            "N065G1X-3.176",
            "N070G0X36.93Z3.254+1",
            "N075Z1.754",
            "N080G1X-3.176",
            "N085G0X37.378Z1.754+1",
            "N090Z.254",
            "N095G1X-3.176",
            "N100G0X38.0Z.254+1",
            "N101 VLMON[2]=0",
            "N105X80.01Z-100.417",
        };
    }
}
